/* 
只能处理外部命令，内建命令不能处理，内建命令要一个一个单独实现 
type命令可以查看命令是外部命令还是内建命令:type cd // 是shell的内建 
*/  
  
#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
  
#define SHELL_CMD_MAX_COUNT        10  
#define SHELL_CMD_MAX_LENGTH       20  
#define SHELL_IN_OUT_FILE_MAX_SIZE 20  
#define SHELL_BUFFER               64  
#define SHELL_PIPE_MAX_COUNT       10  
#define SHELL_PIPE_READ_WRITE      2  
  
typedef unsigned int uint32_t;  
typedef unsigned char uint8_t;  
  
typedef struct command  
{  
  char *arg[SHELL_CMD_MAX_LENGTH]; // 命令行参数，最多10个参数  
  char *input_file; // 存放输入重定向的文件名  
  char *output_file; // 存放输出重定向的文件名  
} cmd_t;  
  
  
/* 以空格符分开命令行字符串 */  
static uint8_t  
shell_parse_cmd_line(char *data, cmd_t *cmd)  
{  
  uint32_t i = 0;  
  
  cmd->input_file = NULL;  
  cmd->output_file = NULL;  
  
  char *token = strtok(data, " ");  
  
  while(token)  
  {  
    /*如果>后面有空格，那么执行完strtok后，空格被替换成'\0',*(p+1)就是'\0'，为假，不执行cmd_buf->out=p+1*/  
    /*如果>后面没有空格，那么执行完strtok后，>符号被替换成'\0'了，直接调用strtok函数*/  
    if(*token == '>')  
    {  
      if(*(token + 1))  
      {  
        cmd->output_file = token + 1;  
      }  
      else  
      {  
        cmd->output_file = strtok(NULL, " ");  
      }  
  
    }  
    else if(*token == '<')  
    {  
      if(*(token + 1))  
      {  
        cmd->input_file = token + 1;  
      }  
      else  
      {  
        cmd->input_file = strtok(NULL, " ");  
      }  
  
    }  
    else  
    {  
      printf("token....\n");  
      /*如果获取的命令行参数不是>或者<,那么就将它们保存在arg中*/  
      cmd->arg[i++] = token;  
  
    }  
    token = strtok(NULL, " ");  
  }  
  
  cmd->arg[i] = NULL;  
  
  return 0;  
}  
  
/* 以管道符分开命令行字符串 */  
static uint8_t  
shell_parse_pipe(char *data, cmd_t cmd[])  
{  
  uint8_t count = 0;  
  char *temp;  
  char *token = strtok_r(data, "|",  &temp);  
  
  while(token)  
  {  
    /*以管道符分开的第一个字符串存放在结构数组cmd[0]中，第二个字符串存放在结构数组cmd[1]中，依次递推*/  
    shell_parse_cmd_line(token, &cmd[count++]);  
    token = strtok_r(NULL, "|", &temp);  
  }  
  
  return count;  
}  
  
/* cd内部命令 */  
int shell_cd_command(char *command, char *path)  
{  
  int return_value = 0;  
  
  
  if(strncmp(command, "cd", 2) == 0)  
    if((return_value = chdir(path)) < 0)  
    {  
      perror("chdir");  
    }  
  return return_value;  
}  
  
static void  
shell_process(void)  
{  
  char cmd_buf[SHELL_BUFFER], pathname[SHELL_BUFFER];  
  cmd_t cmds[SHELL_CMD_MAX_COUNT];  
  pid_t pid; // 进程pid  
  /* 输入输出重定向文件描述符, 管道个数, 10个管道描述符, 管道分隔的命令个数*/  
  uint8_t fd_in, fd_out, pipe_num, pipe_fd[SHELL_PIPE_MAX_COUNT][SHELL_PIPE_READ_WRITE], cmd_num = 0;  
  uint8_t i = 0, j = 0;  
  
  while(1)  
  {  
    memset(pathname, 0, sizeof(pathname));  
    getcwd(pathname, sizeof(pathname)); /* 获取当前工作路径 */  
    printf("[libang--%s--]$", pathname);  
    fflush(stdout); /* 刷新缓冲区，这连续四行只是为了显示好看而已，不要也可以 */  
  
    memset(cmd_buf, 0, sizeof(cmd_buf));  
    fgets(cmd_buf, sizeof(cmd_buf), stdin);  
    cmd_buf[strlen(cmd_buf) - 1] = '\0'; // 获取输入的命令到cmd_buf  
  
    cmd_num = shell_parse_pipe(cmd_buf, cmds);  
  
    shell_cd_command(cmds[0].arg[0], cmds[0].arg[1]); // 处理cd内建命令  
  
    pipe_num = cmd_num - 1;  
    if(pipe_num > SHELL_PIPE_MAX_COUNT)  
    {  
      continue;  
    }  
    /* 一个管道有in和out， 创建pipe_num个管道 */  
    for(i = 0; i < pipe_num; ++i)  
    {  
      pipe(pipe_fd[i]);  
    }  
  
    /* 这一轮循环，创建了pipe_num+1个进程，其中一个父进程，pipe_num个子进程 */  
    for(i = 0; i < cmd_num; ++i)  
    {  
      if((pid = fork()) < 0)  
      {  
        printf("fork fail!\n");  
        exit(1);  
      }  
      if(pid == 0)  
      {  
        break;  
      }  
    }  
  
    /* 有多少个命令就会执行多少个子进程，最终调用exec函数族 */  
    if(pid == 0)  
    {  
      /* 上面循环中，子进程break，所以执行下面的语句，此时i就和循环变量i一样 */  
      if(cmds[i].input_file)  
      {  
        /* 重定向输入 */  
        if((fd_in = open(cmds[i].input_file, O_RDONLY)) < 0)  
        {  
          perror("open fail!\n");  
        }  
        dup2(fd_in, STDIN_FILENO);  
        close(fd_in);  
      }  
  
      if(cmds[i].output_file)  
      {  
        /* 重定向输出 */  
        if((fd_out = open(cmds[i].output_file, O_RDWR | O_CREAT | O_TRUNC), 0644) < 0)  
        {  
          perror("open fail!\n");  
        }  
        dup2(fd_out, STDOUT_FILENO);  
        close(fd_out);  
      }  
      /* 管道是进程间通信的一种方式，输入命令中有管道 */  
      if(pipe_num)  
      {  
        /* 第一个子进程，读入写出，关闭读端，把标准输出重定向到写端*/  
        if(0 == i)  
        {  
          close(pipe_fd[i][0]);  
          dup2(pipe_fd[i][1], STDOUT_FILENO); // 本来执行结果是在标准输出上  
          close(pipe_fd[i][1]);  
  
          /* 关闭掉多余的管道 */  
          for(j = 1; j < pipe_num; ++j)  
          {  
            close(pipe_fd[j][0]);  
            close(pipe_fd[j][1]);  
          }  
        }  
        /* 最后一个子进程，关闭写端，把标准输入重定向到读端 */  
        else if(pipe_num == i)  
        {  
          close(pipe_fd[i - 1][0]);  
          dup2(pipe_fd[i - 1][0], STDIN_FILENO);  
          close(pipe_fd[i - 1][0]);  
          /* 关闭掉多余的管道读写端 */  
          for(j = 0; j < pipe_num - 1; ++j)  
          {  
            close(pipe_fd[j][0]);  
            close(pipe_fd[j][1]);  
          }  
        }  
        /* 1~pipe_num-1, */  
        else  
        {  
          dup2(pipe_fd[i - 1][0], STDIN_FILENO);  
          close(pipe_fd[i - 1][0]);  
  
          dup2(pipe_fd[i][1], STDOUT_FILENO);  
          close(pipe_fd[i][1]);  
  
          for(j = 0; j < pipe_num; ++j)  
          {  
            if((j != i - 1) || j != i)  
            {  
              close(pipe_fd[j][0]);  
              close(pipe_fd[j][1]);  
            }  
          }  
        }  
      }  
  
      /* arg第1个参数是命令，后面的参数是命令选项如:-l */  
      execvp(cmds[i].arg[0], cmds[i].arg);  
    }  
    else // 父进程阻塞  
    {  
      for(i = 0; i < pipe_num; ++i)  
      {  
        close(pipe_fd[i][0]);  
        close(pipe_fd[i][1]);  
      }  
      for(i = 0; i < cmd_num; ++i)  
      {  
        wait(NULL);  
      }  
    }  
  }  
}  
  
int main(int argc, char **argv)  
{  
  shell_process();  
  
  return 0;  
}  


/*
用各种C函数实现一个简单的交互式Shell：
1、给出提示符，让用户输入一行命令，识别程序名和参数并调用适当的exec函数执行程序，待执行完成后再次给出提示符。
2、识别和处理以下符号：简单的标准输入输出重定向（ <和>），先dup2然后exec。管道（|）： Shell进程先调用pipe创建一对管道描述符，然后fork出两个子进程，一个子进程关闭读端，调用dup2把写端赋给标准输出，另一个子进程关闭写端，调用dup2把读端赋给标准输入，两个子进程分别调用exec执行程序，而Shell进程把管道的两端都关闭，调用wait等待两个子进程终止。程序应该可以处理以下命令：
○ls△-l△-R○>○file1○
○cat○<○file1○|○wc△-c○>○file1○
○表示零个或多个空格，△表示一个或多个空格
*/